package com.sinosoft.hydra.um.services;

import cn.com.sinux.spring.commons.SystemConfig;
import cn.com.sinux.spring.exception.BusinessException;
import cn.com.sinux.spring.exception.ServiceException;
import cn.com.sinux.spring.utils.FileUtils;
import cn.com.sinux.spring.utils.SpecificationHelper;
import cn.com.sinux.spring.utils.ValidatorUtils;
import com.alibaba.fastjson.JSONObject;
import com.sinosoft.hydra.logging.LogModules;
import com.sinosoft.hydra.um.LoginUser;
import com.sinosoft.hydra.um.constant.BaseConstant;
import com.sinosoft.hydra.um.domain.model.LabDataParser;
import com.sinosoft.hydra.um.domain.model.ParseData;
import com.sinosoft.hydra.um.domain.permission.TExperimentalData;
import com.sinosoft.hydra.um.repositories.TExperimentalDataDAO;
import com.sinosoft.hydra.um.services.component.dataCollect.DataParser;
import com.sinosoft.hydra.um.services.component.dataCollect.DataReader;
import com.sinosoft.hydra.um.services.component.dataCollect.DataResult;
import com.sinosoft.hydra.um.services.component.dataoutput.DataWriter;
import com.sinosoft.hydra.um.services.component.dataoutput.ExcelDataWriter;
import com.sinosoft.hydra.um.services.component.dataoutput.RowTemplate;
import com.sinosoft.hydra.um.services.component.dataoutput.parsers.ColumnParser;
import jxl.write.*;
import jxl.write.Label;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.time.Second;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.transaction.Transactional;
import java.awt.*;
import java.awt.Font;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

/**
 * Created by Administrator on 2016/7/7.
 */
@Service
public class SysParseService {

    @Autowired
    private FileService fileService;
    @Autowired
    private TExperimentalDataDAO experimentalDataDAO;
    @Autowired
    @Qualifier(value = "parseConvertExecutor")
    private ThreadPoolTaskExecutor parseConvertExecutor;

    @Transactional
    public TExperimentalData doParser(String random, String code, Date startTime, Date endTime, List<String> filePaths, LoginUser loginUser) {
        LabDataParser labDataParser = LabDataParser.valueOf(code);
        if (labDataParser == null) {
            throw new BusinessException("不存在的解析器，不能解析实验数据");
        }

        String filePath = labDataParser.getResultPath();
        final String dataPath = filePath + "/" + random + "/" + code;
        List<TExperimentalData> dataList = experimentalDataDAO.findAll(SpecificationHelper.buildSpecification(new SpecificationHelper.SpecificationBuilder<TExperimentalData>() {

            public void fillParam(Root<TExperimentalData> root, CriteriaBuilder cb, List<Predicate> list) {
                list.add(cb.equal(root.get("experimentalDataPath"), dataPath));
            }
        }));
        //判断是否存在相同解析数据
        if (!dataList.isEmpty()) {
            TExperimentalData experimentalData = dataList.get(0);
            if (BaseConstant.DataParseStatus.FAILURE.name().equals(experimentalData.getStatus())) {
                experimentalData.setStatus(BaseConstant.DataParseStatus.WORKING.name());
                experimentalDataDAO.save(experimentalData);
                _doParser(experimentalData, filePaths, labDataParser, startTime, endTime);
                return experimentalData;
            } else {
                return experimentalData;
            }
        } else {
            TExperimentalData experimentalData = new TExperimentalData();
            experimentalData.setExperimentalDataPath(dataPath);
            experimentalData.setStartTime(startTime);
            experimentalData.setEndTime(endTime);
            experimentalData.setCreateUser(loginUser);
            experimentalData.setHasChart(labDataParser.isHasChart() ? BaseConstant.INTEGER_TRUE : BaseConstant.INTEGER_FALSE);
            experimentalData.setCreateTime(new Date());
            experimentalData.setFilePath(StringUtils.join(filePaths, ","));
            experimentalData.setStatus(BaseConstant.DataParseStatus.WORKING.name());
            experimentalDataDAO.save(experimentalData);
            _doParser(experimentalData, filePaths, labDataParser, startTime, endTime);
            return experimentalData;
        }
    }

    /**
     * 删除未绑定的垃圾实验数据
     *
     * @param uId
     */
    @Transactional
    public void deleteUnBindExperimentalData(final String uId) {
        Specification<TExperimentalData> experimentalDataSpecification = SpecificationHelper.buildSpecification(new SpecificationHelper.SpecificationBuilder<TExperimentalData>() {

            public void fillParam(Root<TExperimentalData> tExperimentalDataRoot, CriteriaBuilder criteriaBuilder, List<Predicate> predicates) {
                predicates.add(tExperimentalDataRoot.get("tProcessRecord").isNull());
                predicates.add(criteriaBuilder.equal(tExperimentalDataRoot.get("createUser").get("id").as(String.class), uId));
            }
        });
        List<TExperimentalData> experimentalDatas = experimentalDataDAO.findAll(experimentalDataSpecification);
        for (TExperimentalData experimentalData : experimentalDatas) {
            String path = fileService.getRelativePath() + experimentalData.getExperimentalDataPath();
            try {
                FileUtils.deleteDirectory(new File(path));
            } catch (IOException e) {
                LoggerFactory.getLogger(LogModules.TRACE_LOGGER).error("删除未绑定的实验数据失败，请手动删除{}", path);
            }
        }
        experimentalDataDAO.delete(experimentalDatas);
    }

    private void _doParser(final TExperimentalData experimentalData, final List<String> filePaths, final LabDataParser labDataParser, final Date startTime, final Date endTime) {
        //反射获取解析器
        DataReader dataReader;
        DataParser dataParser;
        try {
            dataReader = (DataReader) Class.forName(labDataParser.getEngine()).newInstance();
            dataParser = (DataParser) Class.forName(labDataParser.getParser()).newInstance();
        } catch (Exception e) {
            LoggerFactory.getLogger(LogModules.OPT_LOGGER).error("反射创建实例对象失败{}", e.getMessage());
            throw new ServiceException("反射创建数据解析器实例失败，请检查实例对象情况" + e.getMessage());
        }
        //        文件肯定存在
        List<String> paths = new ArrayList<String>();
        for (String filePath : filePaths) {
            paths.add(fileService.getRelativePath() + filePath);
        }
        //调用解析器解析数据
        DataResult _result = null;
        try {
            _result = dataReader.read(paths, startTime, endTime, dataParser, labDataParser);
        } catch (BusinessException bs) {
            throw new BusinessException(bs.getMessage());
        } catch (Exception ex) {
            LoggerFactory.getLogger(LogModules.TRACE_LOGGER).error("实验数据抓取失败，失败原因{}" + ex.getMessage());
            throw new BusinessException("服务器处理实验数据失败,请联系管理员进行数据调试");
        }
        final DataResult dataResult = _result;
        if (dataResult == null) {
            experimentalData.setStatus(BaseConstant.DataParseStatus.FAILURE.name());
            experimentalData.setFailureMsg(BaseConstant.ParseFailureMsg.PARSE_EXCEPTION.name());
            experimentalDataDAO.save(experimentalData);
            return;
        }
        if (dataResult.getExcelData().getData().size() == 0) {
            experimentalData.setStatus(BaseConstant.DataParseStatus.FAILURE.name());
            experimentalData.setFailureMsg(BaseConstant.ParseFailureMsg.DATA_NOT_FOUND.name());
            experimentalDataDAO.save(experimentalData);
            return;
        }
        final String dirPath = fileService.getRelativePath() + experimentalData.getExperimentalDataPath();
        File dir = new File(dirPath);
        dir.mkdirs();
        ParseData parseData = new ParseData();
        if (ValidatorUtils.isArrayNotEmpty(dataResult.getLabels())) {
            for (int t = 0, ll = dataResult.getLabels().size(); t < ll; t++) {
                DataResult.Label label = dataResult.getLabels().get(t);
                if (BaseConstant.LabelType.LABEL_TYPE_IMG.name().endsWith(label.getType())) {
                    File file = new File(label.getText());
                    OutputStream outputStream = null;
                    try {
                        outputStream = new FileOutputStream(dirPath + "/" + file.getName());
                        FileUtils.copyFile(file, outputStream);
                        outputStream.close();
                        label.setText(dirPath + "/" + file.getName());
                    } catch (IOException e) {
                        LoggerFactory.getLogger(LogModules.TRACE_LOGGER).error("实验抓取图片数据失败");
                        throw new BusinessException("实验数据解析图片" + label.getText() + "失败，请重新开始进行！");
                    }
                    label.setText(label.getText().substring(fileService.getRelativePath().length()));
                }
            }
            parseData.setLabels(dataResult.getLabels());
        }
        List<DataResult.TimeLineData> lineData = dataResult.toTimeLineData();
        if (ValidatorUtils.isArrayNotEmpty(lineData)) {
            if (experimentalData.getStartTime() == null) {
                experimentalData.setStartTime(lineData.get(0).getTime());
            }
            if (experimentalData.getEndTime() != null) {
                experimentalData.setStartTime(lineData.get(lineData.size() - 1).getTime());
            }
        }
        List<DataResult.TitleLineData> linDate = null;
        if (labDataParser.isHasChart()) {
            linDate = dataResult.toTitleLineData(lineData);
            parseData.setTimeLineData(linDate);
        }
        saveAsJson(JSONObject.toJSONString(parseData), dirPath + "/data.json");
        experimentalData.setHasChart(labDataParser.isHasChart() ? BaseConstant.INTEGER_TRUE : BaseConstant.INTEGER_FALSE);
        for (String filePath : paths) {
            try {
                FileUtils.forceDelete(new File(filePath));
            } catch (IOException e) {
                LoggerFactory.getLogger(LogModules.TRACE_LOGGER).warn("系统清理已抓取过的实验文件失败，请手动删除{}", filePath);
            }
        }
        //生成excel文件
        try {
            saveAsExcel(dataResult, dirPath + "/data.xls");
        } catch (Exception ex) {
            LoggerFactory.getLogger(LogModules.TRACE_LOGGER).warn("因为异常的原因导致实验的数据excel生成失败，将对导出的报告中实验excel实验附件产生影响{}", ex.getMessage());
            throw new ServiceException("保存为excel文件失败:" + ex.getMessage());
        }
        experimentalData.setStatus(BaseConstant.DataParseStatus.SUCCESS_LOAD_DATA.name());
        experimentalDataDAO.save(experimentalData);

        if (labDataParser.isHasChart()) {
            final List<DataResult.TitleLineData> _linDate = linDate;
            parseConvertExecutor.submit(new Runnable() {

                public void run() {
                    //jfree绘图
                    try {
                        draw(_linDate, dirPath, labDataParser.getTitle());
                        experimentalData.setStatus(BaseConstant.DataParseStatus.SUCCESS_DRAW.name());
                        experimentalDataDAO.save(experimentalData);
                    } catch (Exception ex) {
                        LoggerFactory.getLogger(LogModules.TRACE_LOGGER).warn("因为异常的原因导致实验的图片生成失败，将对导出的报告中实验图片产生影响{}", ex.getMessage());
                    }
                }
            });
        }
    }


    private void saveAsExcel(DataResult dataResult, String filePath){
        DataResult.DataSet dataSet = dataResult.getExcelData();
        RowTemplate<List<String>> rowTemplate = new RowTemplate<List<String>>();
        int i = 0;
        for (String title : dataSet.getTitles()) {
            final int _i = i;
            rowTemplate.addColumnTemplate(new RowTemplate.ColumnTemplate<List<String>>(title, new ColumnParser<List<String>>() {

                public jxl.write.WritableCell parse(int row, int cellNum, List<String> strings) {
                    return new Label(cellNum, row, strings.get(cellNum));
                }
            }));
            i++;
        }
        DataWriter<List<String>> tpBomDataWriter = new ExcelDataWriter<List<String>>(rowTemplate);
        //输出文件
        File outFile = new File(filePath);
        //如果输出目标文件夹不存在，则创建
        if (!outFile.getParentFile().exists()) {
            outFile.getParentFile().mkdirs();
        }
        try {
            tpBomDataWriter.write(filePath, dataSet.getData());
        } catch (Exception e) {
            throw new ServiceException("保存为excel文件失败:" + e.getMessage());
        }
    }

    private void draw(List<DataResult.TitleLineData> lineDatas, String dirPath, String title) {
        TimeSeriesCollection timeSeriesCollection = new TimeSeriesCollection();
        int size = 0;
        for (DataResult.TitleLineData titleLineData : lineDatas) {
            TimeSeries timeSeries = new TimeSeries(titleLineData.getTitle());
            size = titleLineData.getTimes().size();
            for (int i = 0; i < size; i++) {
                timeSeries.addOrUpdate(new Second(titleLineData.getTimes().get(i)), titleLineData.getDoubles().get(i));
            }
            timeSeriesCollection.addSeries(timeSeries);
        }

        JFreeChart jfreechart = ChartFactory.createTimeSeriesChart(title + "实验参数记录", "时间", "指数值", timeSeriesCollection, true, true, true);
        XYPlot xyplot = (XYPlot) jfreechart.getPlot();
        DateAxis dateaxis = (DateAxis) xyplot.getDomainAxis();
        dateaxis.setDateFormatOverride(new SimpleDateFormat("M月d日 HH:mm:ss"));
        dateaxis.setLabelFont(new Font("黑体", Font.BOLD, 14));         //水平底部标题
        dateaxis.setTickLabelFont(new Font("宋体", Font.BOLD, 12));  //垂直标题
        ValueAxis rangeAxis = xyplot.getRangeAxis();//获取柱状
        rangeAxis.setLabelFont(new Font("黑体", Font.BOLD, 15));
        jfreechart.getLegend().setItemFont(new Font("黑体", Font.BOLD, 15));
        jfreechart.getTitle().setFont(new Font("宋体", Font.BOLD, 20));//设置标题字体
        String filePath = dirPath + "/data.png";
        int width = size * 10;
        if (width < 1000) {
            width = 1000;
        } else if (width > SystemConfig.getIntegerProperty("jFreeChart.pic.max.width", 10000)) {
            width = SystemConfig.getIntegerProperty("jFreeChart.pic.max.width", 10000);
        }
        int height = SystemConfig.getIntegerProperty("jFreeChart.pic.height");
        saveAsFile(jfreechart, filePath, width, height);
    }

    private void saveAsJson(String json, String jsonPath) {
        InputStream in = null;
        try {
            in = new ByteArrayInputStream(json.getBytes("UTF-8"));
            FileUtils.write(in, jsonPath);
        } catch (UnsupportedEncodingException e) {
            throw new ServiceException("编码方式转换失败");
        } catch (Exception ex) {
            throw new ServiceException("转换json失败，失败原因" + ex.getMessage());
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    throw new ServiceException("关流失败");
                }
            }
        }

    }

    private void saveAsFile(JFreeChart chart, String outputPath, int width, int height) {
        FileOutputStream out = null;
        try {
            File outFile = new File(outputPath);
            if (!outFile.getParentFile().exists()) {
                outFile.getParentFile().mkdirs();
            }
            out = new FileOutputStream(outputPath);
            ChartUtilities.writeChartAsPNG(out, chart, width, height);
            out.flush();
        } catch (FileNotFoundException e) {
            throw new ServiceException("文件未找到:" + outputPath);
        } catch (IOException e) {
            throw new ServiceException("绘图失败，io异常");
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    throw new ServiceException("关流失败");
                }
            }
        }
    }

}
